#pragma once

#include <memory>

#include "Misc/CommonMacros.h"
#include "Containers/String.h"
#include "Containers/Any.h"
#include "Serialize/StringSerializer.h"
#include "Platform/PlatformInterface.h"

class WHENGINE_API CCommandLineOption
{
public:

  CCommandLineOption(const CString& InName, const CString& InDescription)
  {
    Name=InName;
    Description=InDescription;
    NeedValue=false;
  }

  CCommandLineOption(const CString& InName, const CString& InDescription,const CAny & DefaultValue)
  {
    Name=InName;
    Description=InDescription;
    Type=DefaultValue.GetType();
    Value=DefaultValue;
    NeedValue=true;
  }

  CCommandLineOption(const CString& InName, const CString& InDescription,CType* InType)
  {
    Name=InName;
    Description=InDescription;
    Type=InType;
    NeedValue=true;
  }

  void SetValue(CAny InValue)
  {
      Value=InValue;
  }
protected:
  CString Name;
  CString ShortName;
  CString Description;
  CType* Type=nullptr;
  CAny Value;
  bool NeedValue=false;
  bool bIsSet=false;

  friend class CCommandLineArgumentsParser;

};




class WHENGINE_API CCommandLineArgumentsParser
{
public:

  void AddOption(
    const CString& InName
    ,const CString& InDescription)
  {
    auto Option=std::make_shared<CCommandLineOption>(InName,InDescription);
    AddOption(Option);
  }

  void AddOption(
    const CString& InName
    ,const CString& InDescription
    ,const CAny & DefaultValue)
  {
    auto Option=std::make_shared<CCommandLineOption>(InName,InDescription,DefaultValue);
    AddOption(Option);
  }

  void AddOption(
    const CString& InName
    ,const CString& InDescription
    ,CType* InType)
  {
    auto Option=std::make_shared<CCommandLineOption>(InName,InDescription,InType);
    AddOption(Option);
  }

  TOptional<CString> Parse(int argc, char* argv[])
  {
    auto Arguments=IPlatformInterface::GetPlatformInterface()->ConvertArgsToUnicode(argc, argv);

    for(int ArgIndex=1;ArgIndex<argc;ArgIndex++)
    {
      auto Error=Parse(Arguments[ArgIndex]);
      if(Error.HasValue())
      {
        return Error;
        break;
      }
    }

    //make sure all options have been set
    // for(auto& OptionPair:Options)
    // {
    //   auto& Option=OptionPair.Second;
    //   if(Option->NeedValue && !Option->Value)
    //   {
    //     return U"Parse Error: option "+Option->Name+U" not set\n";
    //   }
    // }

    return TOptional<CString>();
  }

  bool IsOptionSet(const CString& InOptionName)
  {
    if(!Options.Contains(InOptionName))
    {
      return false;
    }

    return Options[InOptionName]->bIsSet;
  }

  template<typename T>
  T GetValue(const CString& InOptionName)
  {
    if(!Options.Contains(InOptionName))
    {
      return T();
    }

    return AnyCast<T>(Options[InOptionName]->Value);
  }

protected:
  void AddOption(std::shared_ptr<CCommandLineOption> InOption)
  {
    Options.Add(InOption->Name,InOption);
    if(InOption->ShortName.GetLength()>0)
    {
      Options.Add(InOption->ShortName,InOption);
    }
  }

  TMap<CString /*name*/, std::shared_ptr<CCommandLineOption>> Options;

  /// @brief 
  /// @param InArgument 
  /// @return Error 
  TOptional<CString> Parse(const CString& InArgument)
  {
    CString OptionName;
    auto Position=InArgument.Find(U'=');
    if(Position!=-1)
    {
      OptionName=InArgument.SubString(0,Position);
    }
    else
    {
      OptionName=InArgument;
    }

    if(!Options.Contains(OptionName))
    {
      return U"Parse "+InArgument +U"Error: unknow option["+OptionName+U"]\n";
    }

    auto& Option=Options[OptionName];
    Option->bIsSet=true;

    if(!Option->NeedValue && Position!=-1)
    {
      if(Option->NeedValue)
      {
        return U"Parse "+InArgument + +U"Error: option " +OptionName+U" need a value followed a '='\n";
      }
      else
      {
        return U"Parse "+InArgument + +U"Error: option " +OptionName+U" do not need any value\n";
      }
    }

    if(!Option->NeedValue)
    {
      return TOptional<CString>();
    }
    

    CString ValueString=InArgument.SubString(Position+1);
    if(ValueString.GetLength()==0)
    {
      return U"Parse "+InArgument + +U"Error: option " +OptionName+U" need a value followed a '='\n";
    }

    Option->SetValue(WH::DeserializeAnyFromString(ValueString,Option->Type));

    return TOptional<CString>();
  }
};

